﻿#define traditional_method

using System;
using System.Collections.Generic;
using System.Linq;
using System.IO;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Storage;

using ArkanoidX.UI;
using ArkanoidX.Sounds;

/*
 * things to remember:
 *  1. high-score table would be worth about 15 marks
 */

namespace ArkanoidX
{
    /// <summary>
    /// This is the main type for your _game
    /// </summary>
    public class MainGame : Microsoft.Xna.Framework.Game
    {
        public GraphicsDeviceManager Graphics;
        SpriteBatch _spriteBatch = null;

        private Texture2D _backgroundTexture;    // texture as the background of the _game

        public LayerManager LayerManager;
        public LevelManager LevelManager;

        /**
         * current level
         */
        public Level _level;
        public int _iCurrLevel;

        /**
         * global variable indicating if the game has gone through startup process
         */
        private bool _started = false;
        
        public LevelTransition _levelTransition = null;


        // game properties
        Rectangle _arenaRegion = default(Rectangle);
        Rectangle _panelRegion = default(Rectangle);

        HashSet<GameComponent> _removeList = new HashSet<GameComponent>();
        HashSet<GameComponent> _addList = new HashSet<GameComponent>();


        // layers
        public Layer BaseLayer { get; set; }
        public Layer BricksLayer { get; set; }
        public Layer BallLayer { get; set; }
        public Layer RacketLayer { get; set; }
        public Layer ShotsLayer { get; set; }
        public Layer TextLayer { get; set; }

        // keyboard state
        public class LastKeyBoardState
        {
            public bool LastEnterPressed = false;
            public bool LastUpPressed = false;
            public bool LastDownPressed = false;
            public bool LastBackPressed = false;
            public bool LastTabPressed = false;
            public bool LastEscPressed = false;

            public bool[] LastLetterPressed = new bool[26];
            public bool[] LastDigitPressed = new bool[10];
        }

        public LastKeyBoardState lkbsUI = new LastKeyBoardState();
        public LastKeyBoardState lkbsGame= new LastKeyBoardState();

        // traits
        public const int TotalLives = 3;


        // records
        public List<PlayerRecord> Records = new List<PlayerRecord>();
        public const int MaxNumRecordsToKeep = 10;

        HighScoreRegister hsr = null;
        HighScores hs = null;
        int FinalScore;

        bool _gameStopped = false;
        Text gameover = null;
        TimeSpan _initGameOver = default(TimeSpan);
        TimeSpan _gameoverShowDur = new TimeSpan(0, 0, 1);

        // sounds
        public SoundResources SoundResources = null;

        // gui
        public Window RootWindow = null;

        // time
        public GameTime currentTime;

        public bool needsUpdateKeys = false;

        public bool paused = false;

        // game history
        public struct PlayerRecord : IComparable<PlayerRecord>
        {
            public int Score;
            public String PlayerName;

            public int CompareTo(PlayerRecord that)
            {
                int res = this.Score.CompareTo(that.Score);
                if (res == 0)
                    res = this.PlayerName.CompareTo(that.PlayerName);
                return res;
            }

            public bool LoadFromLine(String line)
            {
                String[] splitResult = line.Split(':');
                if (splitResult.Length < 2)
                    return false;
                PlayerName = splitResult[0];
                Score = int.Parse(splitResult[1]);
                return true;
            }
        }
        
        public Rectangle GameRectangle
        {
            get
            {
                return new Rectangle(0, 0, Graphics.PreferredBackBufferWidth,
                    Graphics.PreferredBackBufferHeight);
            }
        }

        public Rectangle Arena
        {
            get
            {
                if (_arenaRegion == default(Rectangle))
                {
                    /**
                     * <comments>
                     *  use _grapchis.PreferedBackBuffer<Dimensions> instead of
                     *  Game.Window.ClientBounds because the former gives the 
                     *  real game area
                     * </comments>
                     */
                    _arenaRegion = new Rectangle(0, 0,
                        (int)(Graphics.PreferredBackBufferWidth * 0.7f),
                        Graphics.PreferredBackBufferHeight);
                }
                return _arenaRegion;
            }
        }

        public Rectangle Panel
        {
            get
            {
                if (_panelRegion == default(Rectangle))
                {
                    /**
                     * 
                     */
                    int left = (int)Math.Ceiling(Graphics.PreferredBackBufferWidth * 0.7f);
                    int width = Graphics.PreferredBackBufferWidth - left;
                    _panelRegion = new Rectangle(
                        left, 0, width, Graphics.PreferredBackBufferHeight);
                }
                return _panelRegion;
            }
        }

        public MainGame()
        {
            Graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";

            /**
             * game screen dimension should be set here
             */
            Graphics.PreferredBackBufferWidth = 800;
            Graphics.PreferredBackBufferHeight = 600;

            LevelManager = new LevelManager(this);
            LayerManager = new LayerManager(this);

            // create layers
            LayerManager.CreateLayerAndInsertAt(0);

            BaseLayer = LayerManager.CreateLayerAndStack();
            BricksLayer = LayerManager.CreateLayerAndStack();
            RacketLayer = LayerManager.CreateLayerAndStack();
            ShotsLayer = LayerManager.CreateLayerAndStack();
            BallLayer = LayerManager.CreateLayerAndStack();
            TextLayer = LayerManager.CreateLayerAndStack();

        }

        public void RemoveComponent(GameComponent component)
        {
            LayerManager.RemoveComponent(component);
        }

        public void AddComponent(GameComponent component, Layer layer)
        {
            layer.AddComponent(component);
        }

        public void ClearAllComponents()
        {
            LayerManager.ClearAllComponents();
        }


        /// <summary>
        /// Allows the _game to perform any initialization it needs to before starting to run.
        /// This is where it can query for any required services and load any non-graphic
        /// related content.  Calling base.Initialize will enumerate through any components
        /// and initialize them as well.
        /// </summary>
        protected override void Initialize()
        {
            // TODO: Add your initialization logic here

            base.Initialize();
        }

        /// <summary>
        /// LoadContent will be called once per _game and is the place to load
        /// all of your content.
        /// </summary>
        protected override void LoadContent()
        {
            // Create a new SpriteBatch, which can be used to draw textures.
            _spriteBatch = new SpriteBatch(GraphicsDevice);

            // load the sprite batch to the service collection
            Services.AddService(typeof(SpriteBatch), _spriteBatch);

            // TODO: use this.Content to load your _game content here
            _backgroundTexture = Content.Load<Texture2D>("bgimage");

            // load sounds
            SoundResources = new SoundResources(this);

            // gui
            RootWindow = new Window(this);
        }

        /// <summary>
        /// UnloadContent will be called once per _game and is the place to unload
        /// all content.
        /// </summary>
        protected override void UnloadContent()
        {
            // TODO: Unload any non ContentManager content here
        }

        public void GenerateDefaultDataFile(FileStream fs)
        {
            using (StreamWriter sw = new StreamWriter(fs))
            {
                sw.WriteLine("level1");
                sw.WriteLine("password = ");
                sw.WriteLine("bricks");
                sw.WriteLine("");
                sw.WriteLine("");
                sw.WriteLine("");
                sw.WriteLine("");
                sw.WriteLine("RGB RGB RGB");
                sw.WriteLine("GBRGBRGBRGB");
                /*sw.WriteLine("BRGBRGBRGBR");
                sw.WriteLine("RGBRGBRGBRG");
                sw.WriteLine("GBRGBRGBRGB");
                sw.WriteLine("BRGBRGBRGBR");
                sw.WriteLine("RGBRGBRGBRG");*/
                sw.WriteLine("end_of_bricks");
//#if want_unwanted
                sw.WriteLine("level2");
                sw.WriteLine("password = ");
                sw.WriteLine("bricks");
                sw.WriteLine("");
                sw.WriteLine("");
                sw.WriteLine("");
                sw.WriteLine("");
                sw.WriteLine("R B R BR   ");
                sw.WriteLine("        GB ");
                sw.WriteLine("         GB");
                sw.WriteLine("        GB ");
                sw.WriteLine("      RG   ");
                sw.WriteLine("    RG     ");
                sw.WriteLine("  RB       ");
                sw.WriteLine(" RB        ");
                sw.WriteLine("RG         ");
                sw.WriteLine("RGBRG RGBRG");
                sw.WriteLine("end_of_bricks");
//#endif
            }
        }

        protected void LoadRecords(FileStream fs)
        {
            using (StreamReader sr = new StreamReader(fs))
            {
                while (!sr.EndOfStream)
                {
                    String line = sr.ReadLine();
                    if (line == null)
                        return;

                    PlayerRecord pr = new PlayerRecord();
                    if (pr.LoadFromLine(line))
                    {
                        int index = Records.BinarySearch(pr);
                        if (index < 0)
                        {
                            index = -index - 1;
                        }   // duplicate records allowed
                        Records.Insert(index, pr);
                    }
                }
            }
        }

        public void SaveRecords(FileStream fs)
        {
            using (StreamWriter sw = new StreamWriter(fs))
            {
                foreach (PlayerRecord pr in Records)
                {
                    sw.WriteLine("{0}:{1}", pr.PlayerName, pr.Score);
                }
            }
        }

        public void SaveRecords()
        {
            using (FileStream fs = new FileStream("records.txt", FileMode.Create))
            {
                SaveRecords(fs);
            }
        }

        public bool BuildAndStartLevel(int iLevel)
        {
            /*
             * build level
             */
            this._iCurrLevel = iLevel;
            LevelManager.LevelAttributes la = new LevelManager.LevelAttributes(this._iCurrLevel, "");
            LevelBuilder lb = this.LevelManager.GetLevelBuilder(la);

            this._level = lb.Build();
            this._level.Lives = MainGame.TotalLives;

            if (this._level != null)
            {
                this._level.Start();
                return true;
            }
            return false;
        }

        /// <summary>
        /// Allows the _game to run logic such as updating the world,
        /// checking for collisions, gathering input, and playing audio.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Update(GameTime gameTime)
        {
            currentTime = gameTime;

            // Allows the _game to exit
            if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
                this.Exit();

            // TODO: Add your update logic here

            // update gui
            RootWindow.Update(gameTime);
            UpdateKeys(lkbsUI);

            if (!_started)
            {
                File.Delete("sampleLevels.txt");
                if (!File.Exists("sampleLevels.txt"))
                {
                    using (FileStream fs = new FileStream("sampleLevels.txt", FileMode.Create))
                    {
                        GenerateDefaultDataFile(fs);
                    }
                }
                using (FileStream fs = new FileStream("sampleLevels.txt", FileMode.Open))
                {
                    LevelManager.LoadLevels(fs);
                }

                if (File.Exists("records.txt"))
                {
                    using (FileStream fs = new FileStream("records.txt", FileMode.Open))
                    {
                        LoadRecords(fs);
                    }
                }

                Title title = new Title(this);
                title.Show();
                _started = true;
            }
            else
            {   // go from one level to the next
                if (_level != null && _level.Stopped && _levelTransition == null && !_gameStopped)
                {
                    _iCurrLevel++;
                    LevelManager.LevelAttributes la = new LevelManager.LevelAttributes(_iCurrLevel, "");
                    LevelBuilder lb = LevelManager.GetLevelBuilder(la);

                    if (lb == null)
                    {
                        _gameStopped = true;
                        this.GameThrough();
                    }
                    else
                    {
                        _levelTransition = new LevelTransition(this, _level, lb, new TimeSpan(0, 0, 3));
                        _levelTransition.Start();
                    }
                }
            }

            if (_levelTransition != null)
            {
                if (_levelTransition.Finished)
                {
                    _level = _levelTransition.TargetLevel;
                    _levelTransition = null;
                    if (_level != null)
                    {
                        _level.Start();
                        _started = true;
                    }
                }
            }

            if (gameover != null)
            {
                if (_initGameOver == default(TimeSpan))
                {
                    _initGameOver = gameTime.TotalGameTime;
                }
                if (gameTime.TotalGameTime - _initGameOver >= _gameoverShowDur)
                {
                    gameover.Close();
                    gameover = null;
                    Finish();
                }
            }
            
            if (hsr != null && hsr.state == UI.Window.State.Closed)
            {
                PlayerRecord pr = new PlayerRecord();
                pr.Score = FinalScore;
                pr.PlayerName = hsr.PlayerName;
                hsr = null;

                int index = Records.BinarySearch(pr);
                if (index < 0)
                {
                    index = -index - 1;
                }
                Records.Insert(index, pr);
                if (Records.Count > MaxNumRecordsToKeep)
                {
                    Records.RemoveRange(0, Records.Count - MaxNumRecordsToKeep);
                }

                ShowHighScores();
            }
            else if (hs != null && hs.state == UI.Window.State.Closed)
            {
                hs = null;
                Restart();
            }
            else
            {
                if (!paused)
                {
                    base.Update(gameTime);
                }

                if (_level != null)
                {
                    _level.Update(gameTime);
                }
            }

            UpdateKeys(lkbsGame);
        }


        public void UpdateKeys(LastKeyBoardState lkbs)
        {
            KeyboardState kbs = Keyboard.GetState();
            lkbs.LastDownPressed = kbs.IsKeyDown(Keys.Down);
            lkbs.LastUpPressed = kbs.IsKeyDown(Keys.Up);
            lkbs.LastEnterPressed = kbs.IsKeyDown(Keys.Enter);
            lkbs.LastBackPressed = kbs.IsKeyDown(Keys.Back);
            lkbs.LastTabPressed = kbs.IsKeyDown(Keys.Tab);
            lkbs.LastEscPressed = kbs.IsKeyDown(Keys.Escape);

            for (int i = 0; i < 26; i++)
            {
                lkbs.LastLetterPressed[i] = kbs.IsKeyDown(Keys.A + i);
            }
            for (int i = 0; i < 10; i++)
            {
                lkbs.LastDigitPressed[i] = kbs.IsKeyDown(Keys.D0 + i);
            }
        }

        /// <summary>
        /// This is called when the _game should draw itself.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Draw(GameTime gameTime)
        {
            GraphicsDevice.Clear(Color.CornflowerBlue);

            // TODO: Add your drawing code here
            _spriteBatch.Begin();

            /* Note:                 
             * the pair of _graphics.GraphicsDevice.DisplayMode.Width, _graphics.GraphicsDevice.DisplayMode.Height
             * instead gives the dimensions of the screen the graphic device is currently engaged with
             */
            _spriteBatch.Draw(_backgroundTexture, new Rectangle(0, 0,
                Graphics.PreferredBackBufferWidth, Graphics.PreferredBackBufferHeight),
                Color.LightGray);

#if traditional_method
            base.Draw(gameTime);

#else
            foreach (GameComponent c in this.Components)
            {
                DrawableGameComponent dgc = c as DrawableGameComponent;
                if (dgc != null)
                    dgc.Draw(gameTime);
            }
#endif

            // draw gui
            RootWindow.Draw(gameTime);

            /*
             * Components might be drawn in base.Draw(...) method, 
             * hence the end drawing method for the batch must be placed after that
             */
            _spriteBatch.End();

        }

        public void GameOver()
        {
            ShowGameOver();
        }

        public void GameThrough()
        {
            _gameStopped = true;
            FinalScore = _level.Score;
            _level = null;
            gameover = new Text(this);
            gameover.Color = Color.Red;
            gameover.Value = "Congratulations! You've done it.";
            gameover.Scale = new Vector2(1.5f, 1.5f);
            gameover.Align(this.GameRectangle, UI.Window.AlignType.Center, 0, 0);
            gameover.Show();
            _initGameOver = default(TimeSpan);
        }

        public void Finish()
        {
            int score = FinalScore;
            bool toKeep = false;
            if (Records.Count < MaxNumRecordsToKeep)
            {
                toKeep = true;
            }

            if (!toKeep)
            {
                toKeep = Records[0].Score < FinalScore;
            }

            if (toKeep)
            {
                hsr = new HighScoreRegister(this);
                hsr.DrawColor = new Color(255, 255, 255, 100);
                hsr.Show();
            }
            else
            {
                ShowHighScores();
            }
        }

        public Layer GetTopLayer()
        {
            if (this.LayerManager._layers.Count > 0)
            {
                return this.LayerManager._layers[this.LayerManager._layers.Count - 1];
            }
            else
            {
                return null;
            }
        }

        public void ShowHighScores()
        {
            hs = new HighScores(this, null);
            hs.DrawColor = new Color(255, 255, 255, 100);
            hs.Show();
        }

        public void ShowGameOver()
        {
            _gameStopped = true;
            FinalScore = _level.Score;
            _level = null;
            gameover = new Text(this);
            gameover.Color = Color.Red;
            gameover.Value = "Game Over";
            gameover.Scale = new Vector2(1.5f, 1.5f);
            gameover.Align(this.GameRectangle, UI.Window.AlignType.Center, 0, 0);
            gameover.Show();
            _initGameOver = default(TimeSpan);
        }


        public void Pause()
        {
            paused = true;
        }

        public void Resume()
        {
            paused = false;
        }

        public void Restart()
        {
            RootWindow.CloseAllChildren();
            this.ClearAllComponents();
            Title title = new Title(this);
            title.Show();
            paused = false;
            _gameStopped = false;
            _level = null;
        }

        public void Quit()
        {
            SaveRecords();
            this.Exit();
        }
    }
}
